// Copyright (c) 2021 Presto Labs Pte. Ltd.
// Author: daniel

#include "coin2/exchange/bitbank/order_v1/parser.h"

#include <utility>

#include "coin/proto/coin_query.pb.h"
#include "coin2/exchange/base/order/order.h"
#include "coin2/exchange/bitbank/api_util/bitbank_error.h"
#include "coin2/exchange/util/json_util.h"

namespace coin2::exchange::bitbank::order_v1 {

bool BitbankParser::IsBitbankErrMessage(const JsonValue& data) const {
  /*
  {
    "success": 0,
    "data": {
      "code": 20003
    }
  }
  */
  std::string suc_code = "1";
  if (data.HasMember("success")) {
    suc_code = data["success"].GetString();
  }
  std::string err_code = "0";
  if (data.HasMember("data")) {
    auto obj = data["data"].GetObject();
    if (obj.HasMember("code")) {
      err_code = obj["code"].GetString();
    }
  }
  return suc_code != "1" && err_code != "0";
}

auto BitbankParser::GetOrderSide(std::string_view side) const -> OrderSide {
  if (side == "buy") {
    return coin::proto::OrderSide::BUY_ORDER;
  } else if (side == "sell") {
    return coin::proto::OrderSide::SELL_ORDER;
  } else {
    return coin::proto::OrderSide::UNKNOWN_ORDER_SIDE;
  }
}

auto BitbankParser::GetOrderType(std::string_view type) const -> OrderType {
  if (type == "limit") {
    return coin::proto::OrderType::LIMIT_ORDER;
  } else if (type == "market" || type == "price") {
    return coin::proto::OrderType::MARKET_ORDER;
  } else {
    return coin::proto::OrderType::UNKNOWN_ORDER_TYPE;
  }
}

void BitbankParser::SetOrderStatus(
    const JsonValue& data,
    ProductOrderElement* product_order_element) const {
  // TODO(daniel) - to confirm
  std::string_view status = data["status"].GetString();
  if (status == "UNFILLED" || status == "PARTIALLY_FILLED") {
    product_order_element->set_is_live(true);
  } else if (status == "FULLY_FILLED") {
    product_order_element->set_fully_filled(true);
  } else if (status == "CANCELED_UNFILLED" || status == "CANCELED_PARTIALLY_FILLED") {
    product_order_element->set_cancel_confirmed(true);
  } else {
    LOG(ERROR) << "[ParseRestOrderInfo] Unknown order status! " << status;
  }
}

void BitbankParser::ParseSubmitResponse() {
  const auto order_context = msg_->oc;
  const auto& http_context = msg_->http_context;
  if (!IsValidResponse(http_context)) {
    return;
  }

  result_.set_type(MsgType::SUBMIT_RESPONSE);
  auto resp = result_.mutable_submit_response();
  resp->Clear();
  resp->set_symbol(order_context->order_spec().product);
  resp->set_proc_order_id(order_context->proc_order_id());
  resp->set_http_status(http_context->res.result_int());
  resp->set_timestamp(msg_->timestamp);

  if (http_context->status == AsioHttpContextStatus::kError) {
    LOG(ERROR) << "[BitbankSubmitOrder] AsioHttpContextStatus Error";
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_NETWORK_ERROR);
    resp->set_success(false);
    return;
  }

  JsonDocument document;
  if (!PrepareJsonDocument(&document)) {
    LOG(ERROR) << "[BitbankSubmitOrder] Parsing Error." << msg_->DebugString();
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_API_ERROR);
    resp->set_success(false);
    return;
  }
  if (!document.IsObject() || !document.HasMember("data")) {
    LOG(ERROR) << "[BitbankSubmitOrder] failed. missing field [data]." << JsonToString(document);
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_UNKNOWN_ERROR);
    resp->set_success(false);
    return;
  }
  auto&& obj = document["data"].GetObject();
  if (IsBitbankErrMessage(document)) {
    LOG(ERROR) << "[BitbankSubmitOrder] failed. id: [" << order_context->proc_order_id() << "], "
               << http_context->res;
    auto&& error_code = obj["code"].GetString();
    auto&& error_msg = api_util::BitbankError::GetBitbankErrorMsg(error_code);
    resp->set_exchange_error_code(error_code);
    resp->set_exchange_error_msg(error_msg);
    // TODO(daniel): group error
    auto order_error_code = coin::proto::OrderErrorCode::ORDER_UNKNOWN_ERROR;
    std::string_view error_code_str = error_code;
    if (error_code_str == "10009") {
      order_error_code = coin::proto::OrderErrorCode::API_LIMIT_ERROR;
    } else if (error_code_str == "20001") {
      order_error_code = coin::proto::OrderErrorCode::NONCE_ERROR;
    } else if (error_code_str == "60001") {
      order_error_code = coin::proto::OrderErrorCode::SUBMIT_ORDER_INSUFFICIENT_FUND;
    }
    resp->set_error_code(order_error_code);
    resp->set_success(false);
  } else {
    resp->set_exchange_order_id(obj["order_id"].GetString());
    if (order_mgr_) {
      order_mgr_->ProcessOpenOrderByJson(obj);
    }
    resp->set_success(true);
  }
}

void BitbankParser::ParseCancelResponse() {
  const auto order_context = msg_->oc;
  const auto& http_context = msg_->http_context;
  if (!IsValidResponse(http_context)) {
    return;
  }

  result_.set_type(MsgType::CANCEL_RESPONSE);
  auto resp = result_.mutable_cancel_response();
  resp->Clear();
  resp->set_symbol(order_context->order_spec().product);
  resp->set_proc_order_id(order_context->proc_order_id());
  resp->set_http_status(http_context->res.result_int());
  resp->set_exchange_order_id(order_context->exchange_order_id());
  resp->set_timestamp(msg_->timestamp);

  if (http_context->status == AsioHttpContextStatus::kError) {
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_NETWORK_ERROR);
    resp->set_success(false);
    LOG(ERROR) << "[BitbankCancelOrder] AsioHttpContextStatus Error";
    return;
  }

  JsonDocument document;
  if (!PrepareJsonDocument(&document)) {
    LOG(ERROR) << "[BitbankCancelOrder] Parsing Error." << msg_->DebugString();
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_API_ERROR);
    resp->set_success(false);
    return;
  }
  if (!document.IsObject() || !document.HasMember("data")) {
    LOG(ERROR) << "[BitbankCancelOrder] failed. missing field [data]." << JsonToString(document);
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_UNKNOWN_ERROR);
    resp->set_success(false);
    return;
  }
  auto&& obj = document["data"].GetObject();
  if (IsBitbankErrMessage(document)) {
    LOG(ERROR) << "[BitbankCancelOrder] failed. id: [" << order_context->proc_order_id() << "], "
               << http_context->res;
    auto&& error_code = obj["code"].GetString();
    auto&& error_msg = api_util::BitbankError::GetBitbankErrorMsg(error_code);
    resp->set_exchange_error_code(error_code);
    resp->set_exchange_error_msg(error_msg);
    // TODO(daniel): group error
    auto order_error_code = coin::proto::OrderErrorCode::ORDER_UNKNOWN_ERROR;
    std::string_view error_code_str = error_code;
    if (error_code_str == "50009") {
      order_error_code = coin::proto::OrderErrorCode::ORDER_NOT_FOUND;
    } else if (error_code_str == "50026") {
      order_error_code = coin::proto::OrderErrorCode::ORDER_CANCELED_ALREADY;
    } else if (error_code_str == "50027") {
      order_error_code = coin::proto::OrderErrorCode::ORDER_FILLED_ALREADY;
    }
    resp->set_error_code(order_error_code);
    resp->set_success(false);
  } else {
    if (obj["status"] == "CANCELED_UNFILLED" || obj["status"] == "CANCELED_PARTIALLY_FILLED") {
      resp->set_cancel_confirmed(true);
      if (order_mgr_) {
        order_mgr_->ProcessClosedOrderByJson(obj);
      }
    } else {
      resp->set_cancel_confirmed(false);
    }
    resp->set_success(true);
  }
}

void BitbankParser::ParseBatchCancelResponse() {
  /*
  {
      "success":1,
      "data":{
          "orders":[
              {
                  "order_id":30210246757,
                  "pair":"xrp_jpy",
                  "side":"sell",
                  "type":"limit",
                  "start_amount":"1.0000",
                  "remaining_amount":"1.0000",
                  "executed_amount":"0.0000",
                  "price":"95.491",
                  "average_price":"0.000",
                  "ordered_at":1691057231179,
                  "canceled_at":1691057251168,
                  "status":"CANCELED_UNFILLED",
                  "expire_at":1706609231179,
                  "post_only":true
              }
          ]
      }
  }
  */
  const auto& oc_list = msg_->oc_list;
  const auto& http_context = msg_->http_context;
  if (!IsValidResponse(http_context)) {
    return;
  }

  result_.set_type(MsgType::MULTI_CANCEL_RESPONSE);
  auto* response = result_.mutable_multi_cancel_response();
  response->Clear();

  std::unordered_map<std::string, CancelOrderResponse*> order_id_to_response_map;
  for (const auto* order_context : oc_list) {
    auto* resp = response->add_each_cancel_response();

    resp->set_symbol(order_context->order_spec().product);
    resp->set_proc_order_id(order_context->proc_order_id());
    resp->set_http_status(http_context->res.result_int());
    resp->set_exchange_order_id(order_context->exchange_order_id());
    resp->set_timestamp(msg_->timestamp);
    resp->set_success(false);

    CHECK(order_context->has_exchange_order_id())
        << "Unexpect order_context not have exchange_order_id, please check! order_context:"
        << order_context->ToProto().DebugString();

    CHECK_EQ(order_id_to_response_map.count(order_context->exchange_order_id()), 0)
        << "exchange_order_id duplicate exchange_order_id:" << order_context->exchange_order_id();

    order_id_to_response_map[order_context->exchange_order_id()] = resp;
  }

  if (http_context->status == AsioHttpContextStatus::kError) {
    LOG(ERROR) << "[" << __func__ << "] AsioHttpContextStatus Error";
    for (auto& resp : *(response->mutable_each_cancel_response())) {
      resp.set_error_code(coin::proto::OrderErrorCode::ORDER_NETWORK_ERROR);
    }
    return;
  }

  JsonDocument json;
  if (!PrepareJsonDocument(&json) || !json.IsObject() || !json.HasMember("data") ||
      !json["data"].IsObject() || !json["data"].HasMember("orders") ||
      !json["data"]["orders"].IsArray()) {
    LOG(ERROR) << "[" << __func__ << "] Parse Error\n" << http_context->res;
    for (auto& resp : *(response->mutable_each_cancel_response())) {
      resp.set_error_code(coin::proto::OrderErrorCode::ORDER_API_ERROR);
    }
    return;
  }

  const auto& orders = json["data"]["orders"].GetArray();

  for (const auto& order : orders) {
    if (!order.HasMember("order_id")) {
      LOG(ERROR) << "[" << __func__ << "] Invalid data format\n" << JsonToString(order);
      continue;
    }
    const std::string order_id = order["order_id"].GetString();

    if (order_id_to_response_map.count(order_id) == 0) {
      LOG(ERROR) << "[" << __func__ << "] unknown order_id\n" << JsonToString(order);
      continue;
    }

    auto* resp = order_id_to_response_map[order_id];

    if (order["status"] == "CANCELED_UNFILLED" || order["status"] == "CANCELED_PARTIALLY_FILLED") {
      resp->set_cancel_confirmed(true);
      if (order_mgr_) {
        order_mgr_->ProcessClosedOrderByJson(order);
      }
    } else {
      resp->set_cancel_confirmed(false);
    }
    resp->set_success(true);
  }
}

void BitbankParser::ParseRestOrderInfo(
    const JsonValue& data,
    ProductOrderElement* product_order_element) {
  /*
  {
    "order_id": 0,
    "pair": "string",
    "side": "string",
    "type": "string",
    "start_amount": "string",
    "remaining_amount": "string",
    "executed_amount": "string",
    "price": "string",
    "average_price": "string",
    "ordered_at": 0,
    "status": "string"
  }
  */

  product_order_element->set_market_type(MarketType::Spot);
  product_order_element->set_exchange_type(ExchangeType::Bitbank);
  product_order_element->set_api_version("v1");
  product_order_element->set_exchange_order_id(data["order_id"].GetString());
  product_order_element->set_symbol(data["pair"].GetString());
  product_order_element->set_price(stod(data["price"].GetString()));
  product_order_element->set_qty(stod(data["start_amount"].GetString()));
  product_order_element->set_proc_order_id(0);
  product_order_element->set_side(GetOrderSide(data["side"].GetString()));
  product_order_element->set_order_type(GetOrderType(data["type"].GetString()));
  product_order_element->set_timestamp(msg_->timestamp);
  SetOrderStatus(data, product_order_element);
}

void BitbankParser::ParseRestOrdersInfo() {
  JsonDocument document;
  if (!PrepareJsonDocument(&document)) {
    LOG(ERROR) << "[ParseRestOrdersInfo] Parsing Error. " << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  auto pob = result_.mutable_order_list();
  pob->Clear();

  if (document.IsObject() && IsBitbankErrMessage(document)) {
    LOG(ERROR) << "[ParseRestOrdersInfo] failed. " << JsonToString(document);
    return;
  }
  if (!document.HasMember("data")) {
    LOG(ERROR) << "[ParseRestOrdersInfo] failed. missing field [data]." << JsonToString(document);
    return;
  }
  bool product_order_bundle_found = false;
  const auto& symbols = native_symbols();
  auto&& obj = document["data"].GetObject();
  if (obj.HasMember("orders") && obj["orders"].IsArray()) {
    // handle multiple order infos
    for (const auto& info : obj["orders"].GetArray()) {
      const auto& symbol = info["pair"].GetString();
      if (symbols.count(symbol) == 0) {
        continue;
      }
      auto order = pob->add_each_order();
      ParseRestOrderInfo(info, order);
      product_order_bundle_found = true;
      if (order_mgr_ && ((order->has_fully_filled() && order->fully_filled()) ||
                         (order->has_cancel_confirmed() && order->cancel_confirmed()))) {
        order_mgr_->ProcessClosedOrderByJson(info);
      }
    }
  } else {
    // handle single order info
    const auto& symbol = obj["pair"].GetString();
    if (symbols.count(symbol) > 0) {
      auto order = pob->add_each_order();
      ParseRestOrderInfo(obj, order);
      product_order_bundle_found = true;
      if (order_mgr_ && ((order->has_fully_filled() && order->fully_filled()) ||
                         (order->has_cancel_confirmed() && order->cancel_confirmed()))) {
        order_mgr_->ProcessClosedOrderByJson(obj);
      }
    }
  }
  if (product_order_bundle_found) {
    result_.set_type(MsgType::ORDER_STATUS);
  }
}

void BitbankParser::ParseRestFillOrders() {
  JsonDocument document;
  if (!PrepareJsonDocument(&document)) {
    LOG(ERROR) << "[ParseRestFillOrders] Parsing Error. " << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }
  ParseRestFillInfoList(document);
}

void BitbankParser::ParseRestFillInfoList(const JsonDocument& document) {
  auto pfb = result_.mutable_fill_list();
  pfb->Clear();

  if (document.IsObject() && IsBitbankErrMessage(document)) {
    LOG(ERROR) << "[ParseRestFillInfoList] failed. " << JsonToString(document);
    return;
  }
  if (ParseRestFillInfo(document, pfb)) {
    result_.set_type(MsgType::ORDER_FILL);
  }
}

bool BitbankParser::ParseRestFillInfo(
    const JsonValue& data,
    ProductFillBundle* product_fill_bundle) {
  /*
  {
    "success": 1,
    "data": {
      "trades": [
        {
          "trade_id": 0,
          "pair": "string",
          "order_id": 0,
          "side": "string",
          "type": "string",
          "amount": "string",
          "price": "string",
          "maker_taker": "string",
          "fee_amount_base": "string",
          "fee_amount_quote": "string",
          "executed_at": 0
        }
      ]
    }
  }
  */
  if (!data.HasMember("data")) {
    LOG(ERROR) << "[ParseRestFillInfo] failed. missing field [data]" << JsonToString(data);
    return false;
  }
  auto&& obj = data["data"].GetObject();
  if (!obj.HasMember("trades")) {
    LOG(ERROR) << "[ParseRestFillInfo] failed. missing field [trades]" << JsonToString(data);
    return false;
  }
  bool product_fill_bundle_found = false;
  for (const auto& info : obj["trades"].GetArray()) {
    int64_t fill_timestamp = stod(info["executed_at"].GetString()) * 1'000'000LL;
    auto fill = product_fill_bundle->add_each_fill();
    fill->set_symbol(info["pair"].GetString());
    fill->set_fill_timestamp(fill_timestamp);
    fill->set_exchange_order_id(info["order_id"].GetString());
    fill->set_price(stod(info["price"].GetString()));
    fill->set_qty(stod(info["amount"].GetString()));
    fill->set_fill_id(info["trade_id"].GetString());
    const std::string fill_type = info["maker_taker"].GetString();
    if (fill_type == "maker") {
      fill->set_fill_type(coin::proto::FillType::MAKER_FILL_TYPE);
    } else if (fill_type == "taker") {
      fill->set_fill_type(coin::proto::FillType::TAKER_FILL_TYPE);
    }
    product_fill_bundle_found = true;
    fill->set_fee(stod(info["fee_amount_quote"].GetString()));
  }
  return product_fill_bundle_found;
}

void BitbankParser::ParseRestOpenOrders() {
  JsonDocument document;
  if (!PrepareJsonDocument(&document)) {
    LOG(ERROR) << "[ParseRestOpenOrders] Parsing Error. " << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  auto pob = result_.mutable_order_list();
  pob->Clear();

  if (document.IsObject() && IsBitbankErrMessage(document)) {
    LOG(ERROR) << "[ParseRestOpenOrders] failed. " << JsonToString(document);
    return;
  }
  if (!document.HasMember("data")) {
    LOG(ERROR) << "[ParseRestOpenOrders] failed. missing field [data]." << JsonToString(document);
    return;
  }
  auto&& obj = document["data"].GetObject();
  if (!obj.HasMember("orders")) {
    LOG(ERROR) << "[ParseRestOpenOrders] failed. missing field [orders]." << JsonToString(document);
    return;
  }
  bool product_order_bundle_found = false;
  const auto& symbols = native_symbols();
  for (const auto& info : obj["orders"].GetArray()) {
    const auto& symbol = info["pair"].GetString();
    if (symbols.count(symbol) == 0) {
      continue;
    }
    auto order = pob->add_each_order();
    ParseRestOrderInfo(info, order);
    product_order_bundle_found = true;
    if (order_mgr_) {
      order_mgr_->ProcessOpenOrderByJson(info);
    }
  }
  if (product_order_bundle_found) {
    result_.set_type(MsgType::ORDER_STATUS);
  }
}

auto BitbankParser::ParseBalance(const JsonValue& document) -> AccountBalance {
  RawBalanceMap raw_balance_map;
  auto success = ParseRawBalance(document, &raw_balance_map);
  if (!success) {
    return {};
  }

  AccountBalance acc_balance;
  acc_balance.set_exchange("Bitbank");
  acc_balance.set_market_type("Spot");
  for (const auto& native_currency : symcache_->GetCurrenciesNativeLower()) {
    auto* balance = acc_balance.add_each_balance();
    if (raw_balance_map.count(native_currency) == 0) {
      balance->set_total(0);
      balance->set_hold(0);
      balance->set_available(0);
    } else {
      balance->MergeFrom(raw_balance_map.at(native_currency));
    }
    auto* currency = symcache_->GetCurrencyByNative(native_currency);
    balance->set_currency(currency->currency());
    balance->set_currency_native(currency->native_currency());
    DLOG(INFO) << "BALANCE: " << currency->native_currency() << " " << balance->total() << " "
               << balance->available();
  }
  is_account_balance_initialized_ = true;

  return acc_balance;
}

bool BitbankParser::ParseRawBalance(const JsonValue& document, RawBalanceMap* raw_balance_map) {
  /*
  {
    "success": 1,
    "data": {
      "assets": [
        {
          "asset": "string",
          "free_amount": "string",
          "amount_precision": 0,
          "onhand_amount": "string",
          "locked_amount": "string",
          "withdrawal_fee": "string",
          "stop_deposit": false,
          "stop_withdrawal": false,
        },
        {
          "asset": "jpy",
          "free_amount": "string",
          "amount_precision": 0,
          "onhand_amount": "string",
          "locked_amount": "string",
          "withdrawal_fee": {
              "under": "string",
              "over": "string",
              "threshold": "string"
          },
          "stop_deposit": false,
          "stop_withdrawal": false,
      },
      ]
    }
  }
  */
  if (document.IsObject() && IsBitbankErrMessage(document)) {
    LOG(ERROR) << "[ParseBalance] failed. " << JsonToString(document);
    return false;
  }
  if (!document.HasMember("data")) {
    LOG(ERROR) << "[ParseBalance] failed. " << JsonToString(document);
    return false;
  }
  auto&& data = document["data"].GetObject();
  if (!data.HasMember("assets")) {
    LOG(ERROR) << "[ParseBalance] failed. " << JsonToString(document);
    return false;
  }
  if (!data["assets"].IsArray()) {
    LOG(ERROR) << "[ParseBalance] failed. " << JsonToString(document);
    return false;
  }
  for (const auto& balance : data["assets"].GetArray()) {
    auto&& currency_str = balance["asset"].GetString();
    CurrencyBalance currency_balance;
    currency_balance.set_currency_native(currency_str);
    currency_balance.set_available(stod(balance["free_amount"].GetString()));
    currency_balance.set_hold(stod(balance["locked_amount"].GetString()));
    currency_balance.set_total(stod(balance["onhand_amount"].GetString()));
    raw_balance_map->emplace(currency_balance.currency_native(), currency_balance);
  }

  return true;
}

void BitbankParser::ParseRestAccountBalance() {
  JsonDocument document;
  if (!PrepareJsonDocument(&document)) {
    LOG(ERROR) << "[ParseRestAccountBalance] Parsing Error. " << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  auto account_balance = ParseBalance(document);
  if (!account_balance.has_exchange() || !account_balance.has_market_type()) {
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }
  result_.set_type(MsgType::ACCOUNT_BALANCE);
  *result_.mutable_account_balance() = std::move(account_balance);
}

void BitbankParser::ParseWsMessage() {
  JsonDocument document;
  if (!PrepareJsonDocument(&document)) {
    LOG(ERROR) << "[ParseWsMessage] Parsing Error. " << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }
  DLOG(INFO) << "OG ws msg: " << JsonToString(document);
  // websocket unsupported
}

auto BitbankParser::ParseRawMessage(const RawMessage& msg) -> const ParseResult& {
  result_.Clear();
  msg_ = &msg;
  sub_topic_id_ = static_cast<OrderSubTopicId>(msg_->type);

  if (msg.type == OrderSubTopicId::WS_MESSAGE) {
    ParseWsMessage();
    return result_;
  }

  // must REST_MESSAGE
  switch (msg.http_context->query_type) {
    case OrderSubTopicId::SUBMIT_RESPONSE:
      ParseSubmitResponse();
      break;
    case OrderSubTopicId::CANCEL_RESPONSE:
      ParseCancelResponse();
      break;
    case OrderSubTopicId::MULTI_CANCEL_RESPONSE:
      void ParseBatchCancelResponse();
      break;
    case OrderSubTopicId::REST_ALL_ORDERS:
      // pass
      break;
    case OrderSubTopicId::REST_OPEN_ORDERS:
      ParseRestOpenOrders();
      break;
    case OrderSubTopicId::REST_FILL_ORDERS:
      ParseRestFillOrders();
      break;
    case OrderSubTopicId::REST_ORDERS_INFO:
      ParseRestOrdersInfo();
      break;
    case OrderSubTopicId::REST_ACCOUNT_BALANCE:
      ParseRestAccountBalance();
      break;
    default:
      LOG(ERROR) << "Parsing Error. " << msg_->DebugString();
      break;
  }
  return result_;
}

}  // namespace coin2::exchange::bitbank::order_v1
